mod static_ui;
mod edit; use edit::{update_editing_math, EditMath, ParsingError, ParseResult};
mod infer;

use crate::{egui, math, ui_utils};

use egui::{
    vec2, Color32, Context, Event, FontData, FontDefinitions, FontFamily,
    FontId, Frame, Id, Key, Pos2, Sense, Stroke, Ui, pos2
};
use math::{PropContent, PropSys, Equation, Proposition};
use ui_utils::{window_clicked, IntegrateButton, MessageRenderer};

use self::{edit::EditingBranch, infer::{update_infer_math, InferMath}};

pub fn init_fonts(ctx: &Context) {
    use std::{fs, io::Read};
    let mut data: Vec<u8> = Vec::new();
    let mut file = fs::File::open("fonts/latinmodern-math.otf").unwrap();
    file.read_to_end(&mut data).unwrap();
    // let data = include_bytes!("../fonts/latinmodern-math.otf");
    let font_data = FontData::from_owned(data);
    let mut fonts = FontDefinitions::default();
    fonts
        .font_data
        .insert("latinmodern-math".to_owned(), font_data);
    fonts.families.insert(
        FontFamily::Name("math".into()),
        vec!["latinmodern-math".to_owned()],
    );
    ctx.set_fonts(fonts);
}

#[derive(Debug)]
pub struct MathUiSys {
    new_button_pos: Option<Pos2>,
    prop_sys: PropSys,
    debug: bool,
    focus: Option<Focused>,
    messages: MessageRenderer,
}

#[derive(Debug)]
struct Focused {
    index: usize,
    state: PropState
}
impl Focused {
    fn new(index: usize, state: PropState) -> Self {
        Self { index, state }
    }
}

#[derive(Debug)]
pub enum PropState {
    Editing(EditMath),
    Inferring(InferMath),
    ParseFailed(EditMath, ParsingError),
}

impl MathUiSys {
    pub fn new() -> Self {
        Self {
            new_button_pos: None,
            prop_sys: PropSys::new(),
            debug: false,
            focus: None,
            messages: MessageRenderer::new()
        }
    }
    fn new_prop(&mut self, pos: Pos2) {
        let index = self
            .prop_sys
            .add_prop(PropContent::None, vec![], pos);
        let new_state = PropState::Editing(EditMath::new_root());
        if let Some(ref mut focus) = self.focus {
            if let PropState::Editing(_) | PropState::ParseFailed(..) = focus.state {
                panic!("Allowing creating new a proposition when current focus is not transfered to infer mode. This is a bug of the program");
            }
            focus.index = index;
            focus.state = new_state;
        } else {
            self.focus = Some(Focused::new(index, new_state))
        }
    }
    pub fn update(&mut self, ctx: &Context, ui: &mut Ui) {
        // update propositions
        let seed_id = Id::new("Prop");
        let frame = Frame::window(ui.style());
        let update_unfocused_with_a_focus = |i: usize, prop_sys: &mut PropSys, bg_click_pos: &mut Option<Pos2>, focus: &mut Focused, messages: &mut MessageRenderer| {
            let prop = if let Some(prop) = prop_sys.get(i) {
                prop
            } else {
                return;
            };
            let id = seed_id.with(i);
            let window = egui::Area::new(id)
                .current_pos(prop.rect.min)
                .movable(true);
            let rect = window.show(ctx, |ui| {
                frame.show(ui, |ui| {
                    update_unfocused_prop(ui, prop_sys, i, messages);
                });
            }).response.rect;
            // if the proposition is not removed
            if prop_sys.get(i).is_none() {
                return;
            }
            prop_sys[i].rect = rect;
            if window_clicked(ctx, id) {
                *bg_click_pos = None;
                if let PropState::Editing(ref edit_math) = focus.state {
                    edit_to_infer(edit_math.clone(), &mut prop_sys[focus.index], &mut focus.state);
                }
                if let PropState::ParseFailed(..) = focus.state {
                    let message = "Can't focus on this proposition because the current one cannot be converted to infer mode".to_owned();
                    let pos = ui.input(|i| i.pointer.interact_pos().unwrap());
                    messages.add_message(message, pos, false);
                } else {
                    *focus = Focused::new(i, PropState::Inferring(InferMath::default()))
                }
            };
        };
        if let Some(ref mut focus) = self.focus {
            let i = focus.index;
            let id = seed_id.with(i);
            let area = egui::Area::new(id)
                .current_pos(self.prop_sys[i].rect.min)
                .movable(true);
            
            let inner_res = area.show(ctx, |ui| {
                frame.show(ui, |ui| {
                    update_focused_prop(ui, &mut self.prop_sys, i, focus, &mut self.messages)
                }).inner
            });
            for j in 0..i {
                update_unfocused_with_a_focus(j, &mut self.prop_sys, &mut self.new_button_pos, focus, &mut self.messages)
            }
            for j in i+1..self.prop_sys.contents.len() {
                update_unfocused_with_a_focus(j, &mut self.prop_sys, &mut self.new_button_pos, focus, &mut self.messages)
            }
            if inner_res.inner {
                self.focus = None;
            } else {
                self.prop_sys[i].rect = inner_res.response.rect;
            }
            if window_clicked(ctx, id) {
                self.new_button_pos = None;
            };
        } else {
            for i in 0..self.prop_sys.contents.len() {
                let prop = if let Some(prop) = self.prop_sys.get(i) {
                    prop
                } else {
                    continue;
                };
                let id = seed_id.with(i);
                let window = egui::Area::new(id)
                    .current_pos(prop.rect.min)
                    .movable(true);
                let rect = window.show(ctx, |ui| {
                    frame.show(ui, |ui| update_unfocused_prop(ui, &mut self.prop_sys, i, &mut self.messages));
                }).response.rect;
                if self.prop_sys.contents[i].is_none() {
                    continue;
                }
                self.prop_sys[i].rect = rect;
                if window_clicked(ctx, id) {
                    self.new_button_pos = None;
                    self.focus = Some(Focused::new(i, PropState::Inferring(InferMath::default())))
                };
            }
        }

        // creating new ones
        let response = ui.allocate_response(ui.available_size(), Sense::click());
        if response.clicked() {
            if let Some(ref mut focus) = self.focus {
                if let PropState::Editing(ref edit_math) = focus.state {
                    edit_to_infer(edit_math.clone(), &mut self.prop_sys[focus.index], &mut focus.state);
                }
                if let PropState::ParseFailed(..) = focus.state {
                    let message = "Can't add a new proposition if current editing proposition cannot be converted to infer mode".to_owned();
                    let pos = response.interact_pointer_pos().unwrap();
                    self.messages.add_message(message, pos, false);
                } else {
                    self.focus = None;
                }
            };
            if self.new_button_pos.is_some() {
                self.new_button_pos = None
            } else if self.focus.is_none() {
                self.new_button_pos = response.interact_pointer_pos();
            }
        }
        if let Some(pos) = self.new_button_pos {
            self.show_add_menu(ui, pos);
        }

        // render arrows
        for prop in self.prop_sys.iter() {
            let cp = prop.rect.center();
            let x1 = prop.rect.min.x;
            let x2 = prop.rect.max.x;
            let y1 = prop.rect.min.y;
            let y2 = prop.rect.max.y;
            let k1 = (y2 - y1) / (x2 - x1);
            let k2 = -k1;
            for from_i in &prop.derived_from {
                let from = &self.prop_sys[*from_i];
                let cf = from.rect.center();
                let vfp = cp - cf;
                let c1 = k1 * (cf.x - x1) + y1;
                let c2 = k2 * (cf.x - x1) + y2;
                let p_end = if cf.y < c1 {
                    if cf.y < c2 {
                        let y_end = prop.rect.top();
                        let x_end = (y_end - cf.y) * vfp.x / vfp.y + cf.x;
                        pos2(x_end, y_end)
                    } else {
                        let x_end = prop.rect.right();
                        let y_end = (x_end - cf.x) * vfp.y / vfp.x + cf.y;
                        pos2(x_end, y_end)
                    }
                } else {
                    if cf.y < c2 {
                        let x_end = prop.rect.left();
                        let y_end = (x_end - cf.x) * vfp.y / vfp.x + cf.y;
                        pos2(x_end, y_end)
                    } else {
                        let y_end = prop.rect.bottom();
                        let x_end = (y_end - cf.y) * vfp.x / vfp.y + cf.x;
                        pos2(x_end, y_end)
                    }
                };
                let v = vfp.normalized() * 20.0;
                let p_start = p_end - v;
                ui.painter().arrow(p_start, v, Stroke::new(1.0, Color32::GRAY));
                ui.painter().line_segment([cp, cf], Stroke::new(1.0, Color32::GRAY));
                // ui.painter().arrow(cp, v, Stroke::new(1.0, Color32::GRAY));
            }
        }

        // show popup messages
        self.messages.update(ui);

        // show debug info
        if ctx.input(|i| i.key_pressed(Key::F3)) {
            self.debug = !self.debug;
        }
        egui::Window::new("Debug")
            .open(&mut self.debug)
            .vscroll(true)
            .show(ctx, |ui| {
                ui.collapsing("prop_sys", |ui| {
                    ui.label(format!("{:?}", self.focus));
                    ui.label(format!("{:#?}", self.prop_sys));
                });
                ui.collapsing("messages", |ui| {
                    ui.label(format!("{:#?}", self.messages));
                })
            });

        
    }
    fn show_add_menu(&mut self, ui: &mut Ui, pos: Pos2) {
        egui::Window::new("right_click")
            .fixed_pos(pos)
            .title_bar(false)
            .frame(Frame::none().stroke(Stroke {
                width: 1.0,
                color: Color32::GRAY,
            }))
            .resizable(false)
            .show(ui.ctx(), |ui| {
                let add_button = IntegrateButton::new("New").padding(vec2(3.0, 3.0));
                if ui.add(add_button).clicked() {
                    self.new_prop(pos);
                    self.new_button_pos = None;
                };
            });
    }
}
fn update_unfocused_prop(ui: &mut Ui, prop_sys: &mut PropSys, prop_index: usize, messages: &mut MessageRenderer) {
    let size = 17.0;
    ui.horizontal(|ui| {
        let prop = &prop_sys[prop_index];
        if let PropContent::Eq(equation) = &prop.content {
            ui.scope(|ui| {
                ui.spacing_mut().item_spacing.x = 0.0;
                set_math_font_size(ui, size);
                equation.render(ui, size);
            });
        }
        let response = ui.button("🗙");
        if response.clicked() {
            let result = prop_sys.try_remove_prop(prop_index);
            if let Err(err) = result {
                messages.add_message(err.to_owned(), response.rect.max, false);
            }
        }
    });
}
fn update_focused_prop(ui: &mut Ui, prop_sys: &mut PropSys, prop_index: usize, focus: &mut Focused, messages: &mut MessageRenderer) -> bool {
    ui.horizontal(|ui| {
        ui.vertical(|ui| {
            let prop = &mut prop_sys[prop_index];
            let is_edising = match focus.state {
                PropState::Editing(_) | PropState::ParseFailed(..) => true,
                PropState::Inferring(_) => false,
            };
            let (edit_res, infer_res) = egui::Area::new(Id::new("Switch"))
            .fixed_pos(prop.rect.left_bottom() + vec2(0.0, 7.0))
            .order(egui::Order::Foreground)
            .show(ui.ctx(), |ui| {
                egui::Frame::window(ui.style()).show(ui, |ui| {
                    ui.horizontal(|ui| {
                        (ui.selectable_label(is_edising, "Edit"), ui.selectable_label(!is_edising, "Infer"))
                    }).inner
                }).inner
            }).inner;
            if let PropState::Editing(ref editing_math) = focus.state {
                if infer_res.clicked() {
                    edit_to_infer(editing_math.clone(), prop, &mut focus.state);
                }
            }
            infer_res.on_hover_text("Due to the imperfections of the representations, some symbols may be omitted when switching to \"infer\" mode.");
            if let PropState::Inferring(_) = focus.state {
                if edit_res.clicked() {
                    infer_to_edit(prop,  &mut focus.state);
                }
            }
            match focus.state {
                PropState::Editing(ref mut editing_math) => {
                    update_editing_math(ui, editing_math);
                },
                PropState::Inferring(ref mut infer_math) => {
                    if let Some(new_focus) = update_infer_math(ui, prop_index, prop_sys, infer_math) {
                        *focus = Focused::new(new_focus, PropState::Inferring(InferMath::default()));
                    };
                },
                PropState::ParseFailed(ref mut editing_math, ref err) => {
                    update_editing_math(ui, editing_math);
                    ui.label(format!("⚠️{}", err.message));
                    if ui.input(|i| i.events.iter().find(|e| {
                        if let Event::Key { .. } = &e { true }
                        else { false }
                    }).is_some()) {
                        focus.state = PropState::Editing(editing_math.clone());
                    }
                }
            }
        });
        let response = ui.button("🗙");
        if response.clicked() {
            let result = prop_sys.try_remove_prop(prop_index);
            if let Ok(_) = result {
                return true;
            } else if let Err(err) = result {
                messages.add_message(err.to_owned(), response.rect.max, false);
            }
        }
        false
    }).inner
}

fn edit_to_infer(editing_math: EditMath, prop: &mut Proposition, state: &mut PropState) {
    match editing_math.parse() {
        Ok(parse_result) => match parse_result {
            ParseResult::Prop(prop_content) => {
                *state = PropState::Inferring(InferMath::default());
                prop.content = prop_content;
            },
            ParseResult::Val(_) => {
                *state = PropState::ParseFailed(editing_math, ParsingError::new("Value is not a proposition"));
            }
        },
        Err(e) => {
            *state = PropState::ParseFailed(editing_math, e)
        }
    }
}
fn infer_to_edit(prop: &mut Proposition, state: &mut PropState) {
    if prop.infer_list.len() == 0 && prop.derived_from.len() == 0 && prop.join_infer_list.len() == 0 {
        match &prop.content {
            PropContent::Eq(equation) => {
                let root = equation.to_editing_branch();
                let edit_math = EditMath::new(Vec::new(), root);
                *state = PropState::Editing(edit_math);
            },
            PropContent::None => {}
        }
    }
}
impl Equation {
    fn to_editing_branch(&self) -> EditingBranch {
        let container = Vec::new();
        EditingBranch::Root(container)
    }
}

fn set_math_font_size(ui: &mut Ui, size: f32) {
    ui.style_mut().override_font_id = Some(FontId::new(size, FontFamily::Name("math".into())));
}
